use crate::config::CFG;
use crate::dto::ctx::ReqCtx;
use crate::service::sys_user_online::check_user_online;
use chrono::{Duration, Local};
use hypers::headers::{authorization::Bearer, Authorization};
use hypers::prelude::*;
use hypers::serde_json::json;
use jsonwebtoken::{
    decode, encode, errors::ErrorKind, DecodingKey, EncodingKey, Header, Validation,
};
use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};

pub static KEYS: Lazy<Keys> = Lazy::new(|| {
    let secret = &CFG.jwt.jwt_secret;
    Keys::new(secret.as_bytes())
});

pub struct Keys {
    pub encoding: EncodingKey,
    pub decoding: DecodingKey,
}

impl Keys {
    fn new(secret: &[u8]) -> Self {
        Self {
            encoding: EncodingKey::from_secret(secret),
            decoding: DecodingKey::from_secret(secret),
        }
    }
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct JWTClaims {
    pub id: String,
    pub token_id: String,
    pub name: String,
    pub exp: i64,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct AuthBody {
    token: String,
    token_type: String,
    pub exp: i64,
    exp_in: i64,
}
impl AuthBody {
    fn new(access_token: String, exp: i64, exp_in: i64, token_id: String) -> Self {
        Self {
            token: access_token + &token_id,
            token_type: "Bearer".to_string(),
            exp,
            exp_in,
        }
    }
}

impl JWTClaims {
    pub fn new(id: String, token_id: String, name: String) -> Self {
        let iat = Local::now();
        let exp = iat + Duration::minutes(CFG.jwt.jwt_exp);
        let exp = exp.timestamp();
        Self {
            id,
            token_id,
            name,
            exp,
        }
    }
    pub fn generate_token(&self) -> Result<AuthBody> {
        return match encode(&Header::default(), &self, &KEYS.encoding) {
            Ok(token) => {
                let auth = AuthBody::new(token, self.exp, CFG.jwt.jwt_exp, self.token_id.clone());
                Ok(auth)
            }
            Err(_) => Err(Error::Response(500, json!("JWTToken encode fail!"))),
        };
    }

    pub async fn verify(token: &str) -> Result<Self, Error> {
        match decode::<JWTClaims>(token, &KEYS.decoding, &Validation::default()) {
            Ok(token) => {
                let token_id = token.claims.token_id.clone();
                let (x, _) = check_user_online(token_id).await;
                if x {
                    return Ok(token.claims);
                } else {
                    return Err(Error::Response(401, json!("该账户已经退出")));
                }
            }
            Err(err) => match *err.kind() {
                ErrorKind::InvalidToken => {
                    return Err(Error::Response(401, json!("你的登录已失效，请重新登录")));
                }
                ErrorKind::ExpiredSignature => {
                    return Err(Error::Response(401, json!("你的登录已经过期，请重新登录")));
                }
                _ => {
                    return Err(Error::Response(401, json!(err.to_string())));
                }
            },
        }
    }

    pub async fn checked_token(token: &str) -> Result<JWTClaims> {
        let claims = JWTClaims::verify(token).await;
        match claims {
            Ok(token) => Ok(token),
            Err(e) => Err(Error::Other(e.to_string())),
        }
    }
}

#[hook]
pub async fn jwt_auth(req: &mut Request, next: &mut Next<'_>) -> Result<Response> {
    let token = get_bear_token(req);
    match JWTClaims::checked_token(&token).await {
        Ok(user) => {
            let uri = req.uri();
            let ori_uri_path = uri.path().to_string();
            let method = req.method().to_string();
            let path = ori_uri_path.replacen(&(CFG.server.api_prefix.clone() + "/"), "", 1);
            let path_params = uri.query().unwrap_or("").to_string();
            let data = std::str::from_utf8(req.payload().await?)?.to_string();
            let req_ctx = ReqCtx {
                ori_uri: if path_params.is_empty() {
                    ori_uri_path
                } else {
                    ori_uri_path + "?" + &path_params
                },
                path,
                path_params,
                method,
                user,
                data,
            };
            req.extensions_mut().insert(req_ctx);
            next.next(req).await
        }
        Err(e) => Err(Error::Other(e.to_string())),
    }
}

pub fn get_bear_token(req: &mut Request) -> String {
    let value = req.header_typed_get::<Authorization<Bearer>>().unwrap();
    let bearer_data = value.token();
    let cut = bearer_data.len() - scru128::new_string().len();
    let token = bearer_data[0..cut].to_string();
    token
}
